﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MySql.Data.MySqlClient;
using System.Diagnostics;
using System.IO;

namespace MikroGui
{
    public partial class Form1 : Form
    {
        private string m_present_share = "01PLAGORA00067";
        private int m_time_from;
        private int m_time_to;

        //private int m_start_of_reaction;
        //private int m_finish_of_reaction;

        long m_first_marker;
        long m_second_marker;

        DataTable m_group_four_up;
        DataTable m_group_four_down;

        List<DataRow> m_four_up_signal = new List<DataRow>();
        
        List<DataRow> m_four_down_signal = new List<DataRow>();
        
        DataTable m_doszlo_sprzedaz;

        public Form1()
        {
            InitializeComponent();
            GetVolumeSlices(19, 4);
        }

        /// <summary>
        /// Zwraca rozpiętość kurs
        /// </summary>
        /// <param name="rateTable"></param>
        /// <returns></returns>
        private float GetRateSpread(DataTable rateTable)
        {
            float minRate = float.MaxValue;
            float maxRate = float.MinValue;
            foreach (DataRow dr in rateTable.Rows)
            {
                if ((float)dr["kurs"] < minRate)
                {
                    minRate = (float)dr["kurs"];
                }

                if ((float)dr["kurs"] > maxRate)
                {
                    maxRate = (float)dr["kurs"];
                }
            }

            return maxRate - minRate; // Zwraca różnice miedzy minimalną i maksymalną wartością.
        }

        /// <summary>
        /// Zwraca ilość zleceń kupna
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        private int GetNumberOfOrders(DataTable dt)
        {
            return dt.Rows.Count;
        }

        private List<SignalPeriod> CalculateSignalOccurence(DataTable rateTable)
        {
            //
            int timeBack = 300; // Proporcjonalne do płynności spółki czyli liczby wystawianych zleceń
            float rateDifference = 1.03F; // 3%, Proporcjonalne do rozpiętości kursu danej spółki
            //int lastSignal = 0; // -1, 0, 1 // spadkowy rosnie ma
            bool isRising = false;
            bool isFalling = false;
            
            //DataTable signalOccurence = new DataTable();
            //signalOccurence.Columns.Add("time", typeof(int));
            //signalOccurence.Columns.Add("direction", typeof(short));
            //signalOccurence.Columns.Add("start", typeof(bool));

            SignalPeriod lastRisingSignal = null;
            SignalPeriod lastFallingSignal = null;
            List<SignalPeriod> signalList = new List<SignalPeriod>();

            foreach (DataRow dr in rateTable.Rows)
            {
                float minPastRate = GetMinimumRate(timeBack, (int)dr["time"], rateTable);
                if ((float)dr["kurs"] > minPastRate * rateDifference) // Jesli wiekszy od ostatniego o okreslony próg
                {
                    if (!isRising)
                    {
                        isRising = true;
                        // Add to table as start of rising
                        lastRisingSignal = new SignalPeriod(true, (int)dr["time"], 0);
                        signalList.Add(lastRisingSignal);
                    }
                }
                else
                {
                    if (isRising)
                    {
                        isRising = false;
                        // Add to table as the end of rising series
                        lastRisingSignal.End = (int)dr["time"];
                    }
                }

                float maxPastRate = GetMaximumRate(timeBack, (int)dr["time"], rateTable);

                if ((float)dr["kurs"] * rateDifference < maxPastRate) // Jesli mniejszy od ostatniego o okreslony próg
                {
                    if (!isFalling)
                    {
                        isFalling = true;
                        // Add to table as start of falling
                        lastFallingSignal = new SignalPeriod(false, (int)dr["time"], 0);
                        signalList.Add(lastFallingSignal);
                    }
                }
                else
                {
                    if (isFalling)
                    {
                        isFalling = false;
                        // Add to table as the end of falling series
                        lastFallingSignal.End = (int)dr["time"];
                    }
                }
            }

            return signalList;
        }

        /// <summary>
        /// Zwraca najmnijesza wartosc kurs z podanego okresu
        /// </summary>
        /// <param name="timeBack">ile sekund wstecz patrzymy</param>
        /// <param name="presentTime">akutalny czas</param>
        /// <param name="rateTable">tabela z kursem</param>
        /// <returns></returns>
        private float GetMinimumRate(int timeBack, int presentTime, DataTable rateTable)
        {
            float minRate = float.MaxValue;
            foreach (DataRow dr in rateTable.Rows)
            {
                if ((int)dr["time"] < presentTime && (int)dr["time"] >= presentTime - timeBack)
                {
                    if ((float)dr["kurs"] < minRate)
                    {
                        minRate = (float)dr["kurs"];
                    }
                }
            }

            return minRate;
        }

        /// <summary>
        /// Zwraca największą wartosc kurs z podanego okresu
        /// </summary>
        /// <param name="timeBack">ile sekund wstecz patrzymy</param>
        /// <param name="presentTime">akutalny czas</param>
        /// <param name="rateTable">tabela z kursem</param>
        /// <returns></returns>
        private float GetMaximumRate(int timeBack, int presentTime, DataTable rateTable)
        {
            float maxRate = float.MinValue;
            foreach (DataRow dr in rateTable.Rows)
            {
                if ((int)dr["time"] < presentTime && (int)dr["time"] >= presentTime - timeBack)
                {
                    if ((float)dr["kurs"] > maxRate)
                    {
                        maxRate = (float)dr["kurs"];
                    }
                }
            }

            return maxRate;
        }

        public void CalculateRateSignals()
        {
            // Wymagane jest zdecydowanie na jeden sposób określania grup. Z okresu ostatnich np. 10 minut
            //(różne dla różnych spółek, ale stałe w obrębie jednej spółki), brany jest najniższy kurs spółki i
            //porównywany są z aktualnym. Jeśli jest większy o np. 3% (różne dla różnych spółek, ale stałe w
            //obrębie jednej spółki) to jest sygnał wzrastającego kursu. Jeśli w przedziale 60 sekund - 10 minut
            //(różne dla różnych spółek, ale stałe w obrębie jednej spółki) od wystąpienia tego sygnału
            //wystawione zostanie zlecenie kupna zaliczymy to zlecenie i ten wolumen do Grupy I. Jeśli
            //wystawione zostanie zlecenie sprzedaży to zakwalifikujemy do Grupy II.

            // sygnaly 
            DataTable kurs = GetRateTable();

            DataTable dt = new DataTable();

            List<int> lastDirections = new List<int>();

        }

        /// <summary>
        /// Funkcja zwraca kurs dla danej spółki
        /// </summary>
        /// <param name="share">kod spółki</param>
        /// <returns></returns>
        public DataTable GetRateTable(string share)
        {
            MySqlDataAdapter adapter = new MySqlDataAdapter(
                    String.Format(@"SELECT * FROM (Select transakcja_kurs1 as kurs, time FROM (SELECT * FROM notowania WHERE channel_id = '{0}' AND transakcja_kurs1 <> 0 ) t order by time, id desc) t GROUP BY time", 
                    share),
                Properties.Settings.Default.mikro_zleceniaConnectionStringRoot);
            
            DataTable kurs = new DataTable();
            adapter.Fill(kurs);
            dataGridKurs.DataSource = kurs;
            return kurs;
        }

        public DataTable GetRateTable()
        {
            return GetRateTable(m_present_share);
        }

        /// <summary>
        /// 
        /// </summary>
        public void CalculateZlecenia()
        {
            float sameLimit = 0;// (float)dt.Rows[0]["sprzedaz_limit"];
            // Lista która zawiera (liste z wierszami które mają ten sam limit i są po sobie)
            List<List<DataRow>> all_limits = new List<List<DataRow>>();
            // tymczasowa lista z tym samym limitem
            List<DataRow> sameLimitList = null;
            
            DataTable dt = new DataTable();
            MySqlDataAdapter adapter = new MySqlDataAdapter(string.Format(
                @"(select id, time, `sprzedaz_oferty1` AS 'sprzedaz_oferty', `sprzedaz_limit1` AS sprzedaz_limit,`sprzedaz_wolumen1` AS sprzedaz_wolumen FROM notowania where sprzedaz_limit1 <> 0 and channel_id='{0}')
                UNION ALL
                (select id,  time, `sprzedaz_oferty2` , `sprzedaz_limit2` ,`sprzedaz_wolumen2` FROM notowania where sprzedaz_limit2 <> 0 and channel_id='{1}')
                UNION ALL
                (select id,  time, `sprzedaz_oferty3` , `sprzedaz_limit3` ,`sprzedaz_wolumen3` FROM notowania where sprzedaz_limit3 <> 0 and channel_id='{2}')
                UNION ALL
                (select id,  time, `sprzedaz_oferty4` , `sprzedaz_limit4` ,`sprzedaz_wolumen4` FROM notowania where sprzedaz_limit4 <> 0 and channel_id='{3}')
                UNION ALL
                (select id,  time, `sprzedaz_oferty5` , `sprzedaz_limit5` ,`sprzedaz_wolumen5` FROM notowania where sprzedaz_limit5 <> 0 and channel_id='{4}') order by sprzedaz_limit, id", m_present_share, m_present_share, m_present_share, m_present_share, m_present_share),
                Properties.Settings.Default.mikro_zleceniaConnectionStringRoot);
            adapter.Fill(dt);

            // iterujemy po zmianach w zleceniach sprzedazy pobranych z bazy
            foreach (DataRow dr in dt.Rows)
            {
                if (sameLimit != (float)dr["sprzedaz_limit"]) // jesli nowy limit
                {
                    sameLimitList = new List<DataRow>(); 
                    all_limits.Add(sameLimitList);
                    sameLimit = (float)dr["sprzedaz_limit"];
                }

                sameLimitList.Add(dr); // dodanie wiersza do listy tych wierszy z tym samym limitem
            }

            // Tabela zawierająca zdarzenia przyjscia do systemu nowych zlecen sprzedazy
            m_doszlo_sprzedaz = new DataTable(); 
            m_doszlo_sprzedaz.Columns.Add("limit", typeof(float));
            m_doszlo_sprzedaz.Columns.Add("time", typeof(int));
            m_doszlo_sprzedaz.Columns.Add("wolumen_zmiana", typeof(long));

            foreach (List<DataRow> sameLimitsVariable in all_limits)
            {
                bool firstFromLimiList = true;
                long previousVolume = 0;
                int previosNumberOfOffers = 0;

                foreach (DataRow dr in sameLimitsVariable)
                {
                    long rowVolume = (long)dr["sprzedaz_wolumen"];
                    int numberOfOffers = (int)dr["sprzedaz_oferty"];
                    float rowLimit = (float)dr["sprzedaz_limit"];
                    int rowTime = (int)dr["time"];
                    int rowId = (int)dr["id"];
                    if (previousVolume != rowVolume || firstFromLimiList) // nowy limit lub zmiana wolumenu w stosunku do poprzedniego.
                    {
                        firstFromLimiList = false;

                        if (numberOfOffers == 1 && previosNumberOfOffers == 1) // Nie zmienila sie liczba ofert, a zmienil sie wolumen
                        {
                            // w polowie uznajemy ze spadlo na zero
                            // 
                        }
                        else
                        {
                            if (rowVolume > previousVolume) // doszla oferta, na pewno ktos dolozyl
                            {
                                if (numberOfOffers - previosNumberOfOffers > 1) // Złożono więcej niż jedno zlecenie
                                {
                                    // Dzielimy wolumen na zlecenia od poszczególnych inwestorów i każde zapisujemy
                                    long[] volumes = GetVolumeSlices(rowVolume - previousVolume, numberOfOffers - previosNumberOfOffers);
                                    foreach (long volumeItem in volumes)
                                    {
                                        DataRow newAddedRow = m_doszlo_sprzedaz.NewRow();
                                        newAddedRow["limit"] = rowLimit;
                                        newAddedRow["time"] = rowTime;
                                        newAddedRow["wolumen_zmiana"] = volumeItem;
                                        m_doszlo_sprzedaz.Rows.Add(newAddedRow);
                                    }
                                }
                                else
                                {
                                    // do tabeli. t, roznica wol, (1 oferta)
                                    // byla decyzja
                                    DataRow newAddedRow = m_doszlo_sprzedaz.NewRow();
                                    newAddedRow["limit"] = rowLimit;
                                    newAddedRow["time"] = rowTime;
                                    newAddedRow["wolumen_zmiana"] = rowVolume - previousVolume;
                                    m_doszlo_sprzedaz.Rows.Add(newAddedRow);
                                }
                            }
                        }
                    }
                    previousVolume = rowVolume;
                    previosNumberOfOffers = numberOfOffers;
                }        
            }

            dataGridDoszlo.DataSource = m_doszlo_sprzedaz;
        }

        private long[] GetVolumeSlices(long volumeToSlice, int numberOfOffers)
        {
            long middle = volumeToSlice / numberOfOffers; // dzielimy równo
            long rest = volumeToSlice % numberOfOffers;
            long[] result = new long[numberOfOffers];

            for (long i = 0; i < numberOfOffers; i++)
            {
                result[i] = middle + i - (numberOfOffers - i - 1);  // Aby zwrócone wartości różniły się między sobą.
            }

            result[numberOfOffers - 1] += rest; // Do ostatniej dodajemy co zostało

            return result;
        }

        public void CalculateGroups(DataTable addedOrders, List<SignalPeriod> rateSignals, int[] volumeSlices)
        {
            SignalPeriod actualSignalPeriod = rateSignals[0];
            int i_next = 1;
            
            foreach (DataRow dr in addedOrders.Rows)
            {
                if (true)
                {
                    
                }
            }

            int i_signal = 0;
            int last_signal_time;
            int time_from = 60; // seconds
            int time_to = 10 * 60; // 10m
            //float last_four_up_signal_kurs;

            m_group_four_up = new DataTable();
            m_group_four_up.Columns.Add("limit", typeof(float));
            m_group_four_up.Columns.Add("time", typeof(int));
            m_group_four_up.Columns.Add("wolumen_zmiana", typeof(long));

            m_group_four_down = new DataTable();
            m_group_four_down.Columns.Add("limit", typeof(float));
            m_group_four_down.Columns.Add("time", typeof(int));
            m_group_four_down.Columns.Add("wolumen_zmiana", typeof(long));
            
            //if (m_four_up_signal.Count > 0)
            {
                last_signal_time = (int)m_four_up_signal[i_signal]["time"];
                i_signal++;
                //last_four_up_signal_kurs = (float)m_four_up_signal[0]["kurs"];
            }

            foreach (DataRow row in m_doszlo_sprzedaz.Rows)
            {
                // Searching for 4up signal.
                int actual_time = (int)row["time"];
                if (actual_time > last_signal_time + time_from && actual_time < last_signal_time + time_to)// between 10seconds and 3 minutes after strong signal
                {
                    m_group_four_up.Rows.Add(row.ItemArray);
                }

                if (actual_time > last_signal_time + time_to) // jesli pozniejszy niz ostatni sygnal ktory analizowalismy.
	            {
                    if (i_signal < m_four_up_signal.Count)
                    {
                        last_signal_time = (int)m_four_up_signal[i_signal]["time"];
                        i_signal++;
                    }
                }
            }

            /*
            i_signal = 0;

            last_signal_time = (int)m_four_down_signal[i_signal]["time"];
            i_signal++;
            

            // Down czyli wycofane zlecenia kupna
            foreach (DataRow row in m_odeszlo_sprzedaz.Rows)
            {
                // Searching for 4 down signal.
                int actual_time = (int)row["time"];
                if (actual_time > last_signal_time + time_from && actual_time < last_signal_time + time_to)// between 10seconds and 3 minutes after strong signal
                {
                    m_group_four_up.Rows.Add(row.ItemArray); 
                    StringBuilder sb = new StringBuilder();
                    sb.Append(String.Format("Time: {0} wycofano zlecenie na {1} po cenie {2} jako reakcje na sygnal sprzedazy z time: {3}\n\n", 
                        row["time"].ToString(), row["wolumen_zmiana"].ToString(), row["limit"].ToString(), last_signal_time.ToString()));
                    rtbGroups.Text += sb.ToString();
                }

                if (actual_time > last_signal_time + time_to) // jesli pozniejszy niz ostatni sygnal ktory analizowalismy.
                {
                    if (i_signal < m_four_down_signal.Count)
                    {
                        last_signal_time = (int)m_four_up_signal[i_signal]["time"];
                        i_signal++;
                    }
                }
            }
             */
        }

        /// <summary>
        /// Oblicza granice przedziałów dla wielkości zleceń
        /// </summary>
        private long[] CalculateVolumeBoundaries()
        {
            // Lista wszystkich wolumenów zleceń kupna które zostały złożone.
            List<long> volumes = new List<long>();
            foreach (DataRow row in m_doszlo_sprzedaz.Rows)
            {
                volumes.Add( (long) row["wolumen_zmiana"]);
            }

            volumes.Sort();

            // Wg ilosci, 
            m_first_marker = volumes[volumes.Count / 3];
            m_second_marker = volumes[volumes.Count * 2 / 3];


            // Mozliwość dodania sprawdzania odchylenia standardowego w każdych przedzialach. TODO
         

            return new long[] { m_first_marker, m_second_marker };
        }

        private void button1_Click(object sender, EventArgs e)
        {
            CalculateZlecenia();
            CalculateVolumeBoundaries();
            CalculateRateSignals();
            //CalculateGroups();
        }

        private void button2_Click(object sender, EventArgs e)
        {

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            MySqlDataAdapter adapter = 
                new MySqlDataAdapter(
                    "SELECT channel_id, COUNT(channel_id) " +
                    "FROM notowania " +
                    "GROUP BY channel_id " + 
                    "HAVING COUNT(channel_id) > 2", 
                    Properties.Settings.Default.mikro_zleceniaConnectionStringRoot
                );

            DataTable names = new DataTable();
            adapter.Fill(names);
            

            //cmbShareSelect.DataSource = names;
            //cmbShareSelect.DisplayMember = "channel_id";
            //cmbShareSelect.ValueMember = "channel_id";

            List<NameIdPair> nameIds = 
                names.AsEnumerable()
                .Select(x => new NameIdPair((string)x["channel_id"]))
                .OrderBy(x=>x.Name)
                .ToList();

            cmbShareSelect.DataSource = nameIds;
            cmbShareSelect.DisplayMember = "Name";
            cmbShareSelect.ValueMember = "Id";
        }

        private void cmbShareSelect_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cmbShareSelect.SelectedValue != null)
            {
                //DataTable dt = (DataTable) cmbShareSelect.DataSource;
                //m_present_share = (string)dt.Rows[cmbShareSelect.SelectedIndex]["channel_id"];

                List<NameIdPair> nameIds = (List<NameIdPair>)cmbShareSelect.DataSource;
                m_present_share = nameIds[cmbShareSelect.SelectedIndex].Id;
            }
        }

        private void EstymujButton_Click(object sender, EventArgs e)
        {
            MatlabCommandLineAutomation ml = MatlabCommandLineAutomation.GetDefault();

            Process matlab = new Process();
            matlab.StartInfo.UseShellExecute = true;
            matlab.StartInfo.FileName = ml.GetMatlabExacutablePath();
            matlab.StartInfo.Arguments = ml.GetMatlabStartArguments() + ml.GetMatlabExecuteCommandArgument();
            matlab.Start();
        }
    }
}


// Sprawdzic dla Biotonu, ile potrwaja obliczenia.
// Podac ile zajmuja obliczenia na moim pececie.